package org.acegisecurity.vote;

import org.acegisecurity.Authentication;
import org.acegisecurity.ConfigAttribute;
import org.acegisecurity.ConfigAttributeDefinition;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.util.ExpressionEvaluator;
import org.acegisecurity.util.IllegalExpressionException;
import org.acegisecurity.util.expressioncompiler.LogicalExpressionCompiler;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * <p>
 * The ConditionalRoleVoter expects 1 Role in the form of a
 * {@link ConfigAttribute#getAttribute()} to be present in the security configuration.
 * The role is in the form of an expression:<br/> Expressions may contain the
 * keywords (reserved words): AND, OR and NOT (case insensitive)<br/>
 * Delimiters are: " " (space), "(" and ")"<br/> e.g.: ROLE_A and ROLE_B and
 * (ROLE_C or ROLE_D)<br/>
 * </p>
 * <p>
 * Comma separated roles are not supported. They are not necessary because one
 * can create one expression that decides / describes it all.
 * </p>
 * <p>
 * It always votes. It does not abstain from voting. An expression should always
 * evaluate to ACCESS_GRANTED or ACCESS_DENIED.<br/>
 * </p>
 * <p>
 * All comparisons of roles are case sensitive.
 * </p>
 * 
 * @author Ivo Woltring
 * @author Jamie Craane
 */
public class ConditionalRoleVoter implements AccessDecisionVoter {
	private static final Log logger = LogFactory.getLog(ConditionalRoleVoter.class);
	private ExpressionEvaluator evaluator = new LogicalExpressionCompiler();
	
	
	/**
	 * Setter for the {@link ExpressionEvaluator} implementation we want to use.
	 * @param evaluator  the  ExpressionEvaluator to pass
	 */
	public void setEvaluator(ExpressionEvaluator evaluator) {
		this.evaluator = evaluator;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.acegisecurity.vote.AccessDecisionVoter#vote(org.acegisecurity.Authentication,
	 *      java.lang.Object, org.acegisecurity.ConfigAttributeDefinition)
	 */
	public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) {
		boolean accessGranted; //false is default value;
		final String expression = ((ConfigAttribute) config.getConfigAttributes().next()).getAttribute();
		if (logger.isDebugEnabled()) {
			logger.debug("Expression: " + expression);
			final GrantedAuthority[] authorities = authentication.getAuthorities();
			logger.debug("This user has the following roles:");
			for (int i = 0; i < authorities.length; i++) {
				logger.debug(authorities[i]);
			}
			logger.debug("=============");
		}
		
		//One can save the compiled expression...
		//LogicalExpressionCompiler.CompiledExpression compiledExpression = LogicalExpressionCompiler.parse(expression);
			try {
				accessGranted = evaluator.match(expression, authentication);
				if (logger.isDebugEnabled()) {
					if (accessGranted) {
						logger.debug("Access is granted.");
					} else {
						logger.debug("Access is denied.");
					}
				}
			} catch (final IllegalExpressionException e) {
				logger.error(e);
				accessGranted = false;
			}
		return accessGranted ? ACCESS_GRANTED : ACCESS_DENIED;
	}

	/**
	 * The specified role is parsed as a logical expression 
	 * so no specific requirements exists for the role.
	 * @return always <code>true</code>
	 */
	public boolean supports(ConfigAttribute attribute) {
		return true;
	}

	/**
	 * This implementation supports any type of class, because it does not query
	 * the presented secure object.
	 * 
	 * @param clazz the secure object
	 * 
	 * @return always <code>true</code>
	 */
	public boolean supports(Class clazz) {
		return true;
	}
}
